# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Vulnerabilities::StarboardVulnerabilityCreateService, feature_category: :vulnerability_management do
  let(:agent) { create(:cluster_agent) }
  let(:project) { agent.project }
  let(:user) { agent.created_by_user }

  let_it_be(:params) do
    {
      vulnerability: {
        name: 'CVE-123-4567 in libc',
        description: 'Vulnerability description',
        severity: 'high',
        confidence: 'unknown',
        location: {
          image: 'alpine:latest',
          dependency: {
            version: '0.1.0',
            package: {
              name: 'libc'
            }
          },
          kubernetes_resource: {
            namespace: 'production',
            kind: 'deployment',
            name: 'nginx',
            container: 'nginx'
          }
        },
        identifiers: [
          {
            type: 'cve',
            name: 'CVE-123-4567',
            value: 'CVE-123-4567'
          }
        ]
      },
      scanner: {
        id: 'starboard_trivy',
        name: 'Trivy (via Starboard Operator)',
        vendor: {
          name: 'GitLab'
        }
      }
    }
  end

  subject { described_class.new(agent, params: params).execute }

  context 'with authorized user' do
    before do
      project.add_developer(user)
    end

    context 'with feature enabled' do
      let(:vulnerability) { subject.payload[:vulnerability] }

      before do
        stub_licensed_features(security_dashboard: true)
      end

      it 'creates Vulnerability' do
        expect { subject }.to change(Vulnerability, :count).by(1)
      end

      it 'has correct data' do
        expect(vulnerability.report_type).to eq("cluster_image_scanning")
        expect(vulnerability.title).to eq(params.dig(:vulnerability, :name))

        finding = vulnerability.finding
        expect(finding.description).to eq(params.dig(:vulnerability, :description))
        expect(finding.severity).to eq(params.dig(:vulnerability, :severity))
        expect(finding.confidence).to eq(params.dig(:vulnerability, :confidence))
        expect(finding.location['image']).to eq('alpine:latest')
        expect(finding.location.dig('dependency', 'package', 'name')).to eq('libc')
        expect(finding.location.dig('dependency', 'version')).to eq('0.1.0')
        expect(finding.location['kubernetes_resource']).to eq(
          {
            'namespace' => 'production',
            'kind' => 'deployment',
            'name' => 'nginx',
            'container' => 'nginx'
          }
        )

        expect(finding.metadata['location']).to eq(finding.location)

        scanner = finding.scanner
        expect(scanner.external_id).to eq(params.dig(:scanner, :id))
        expect(scanner.name).to eq(params.dig(:scanner, :name))
        expect(scanner.vendor).to eq(params.dig(:scanner, :vendor, :name))
      end

      it 'sets cluster_agent.has_vulnerability to true' do
        expect { subject }.to change { agent.reload.has_vulnerabilities }.from(false).to(true)
      end

      context 'when there is an identifier for a different project' do
        let_it_be(:name) { params.dig(:vulnerability, :identifiers).first[:name] }
        let_it_be(:other_identifier) { create(:vulnerabilities_identifier, name: name) }

        it "does not reuse another project's identifier" do
          subject

          expect(vulnerability.finding.identifiers).to match_array([Vulnerabilities::Identifier.last])
        end

        it 'creates a new identifier' do
          expect { subject }.to change { Vulnerabilities::Identifier.count }.by(1)
        end
      end

      context 'when there is a scanner for a different project' do
        let!(:other_scanner) { create(:vulnerabilities_scanner, external_id: params.dig(:scanner, :id)) }

        it "does not reuse another project's scanner" do
          subject

          expect(vulnerability.finding.scanner).not_to eq(other_scanner)
        end

        it 'creates a new scanner' do
          expect { subject }.to change { Vulnerabilities::Scanner.count }.by(1)
        end
      end
    end

    context 'with feature disabled' do
      before do
        stub_licensed_features(security_dashboard: false)
      end

      it 'raises AccessDeniedError' do
        expect { subject }.to raise_error(Gitlab::Access::AccessDeniedError)
      end
    end

    context 'with unauthorized user' do
      before do
        project.add_reporter(user)
      end

      it 'raises AccessDeniedError' do
        expect { subject }.to raise_error(Gitlab::Access::AccessDeniedError)
      end
    end
  end
end
